﻿// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Local

/* Code128.cs -- штрих-код, поддерживающий не только цифры, но и буквы латинского алфавита
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System.Collections.Generic;
using System.Drawing;

#endregion

#nullable enable

namespace AM.Drawing.Barcodes;

//
// https://ru.wikipedia.org/wiki/Code_128
//
// Стандарт штрихкода Code 128 существенно отличается от таких широко
// распространённых стандартов штрихового кода, как, например, EAN.
// Отличия заключаются, прежде всего, в возможности кодирования
// не только цифр, но и букв латинского алфавита, а также специальных
// символов. Кроме того, цифровой код в формате Code 128 становится
// очень компактным, что достигается за счёт «двойной упаковки» данных,
// когда две цифры записываются в один шаблон штрихкода. Буквенные
// символы кодируются обычным — «одиночным» — способом, что делает
// буквенный код в формате Code 128 вдвое длиннее цифрового.
//
// Штриховой код Code 128 включает в себя 107 символов, из которых
// 103 символа данных, 3 стартовых и 1 остановочный (стоп) символ.
// Для кодирования всех 128 символов ASCII предусмотрено три комплекта
// символов штрихового кода Code 128 — A, B и C, которые могут
// использоваться внутри одного штрихкода.
//
// * 128A — символы в формате ASCII от 00 до 95 (цифры от «0» до «9»
//   и буквы от «A» до «Z»), специальные символы и символы FNC 1-4;
// * 128B — символы в формате ASCII от 32 до 127 (цифры от «0» до «9»,
//   буквы от «A» до «Z» и от «a» до «z»), специальные символы
//   и символы FNC 1-4;
// * 128C — числа от 00 до 99 (двузначное число кодируется одним
//   символом) и символ FNC 1.
//
// Технические требования к символике штрихового кода Code 128,
// показатели символики, кодирование знаков данных, размеры,
// алгоритмы декодирования, параметры применения и строки-префиксы
// и идентификатора символики в России регламентируются
// ГОСТ 30743-2001 (ИСО/МЭК 15417—2000) (Заменен на
// ГОСТ ISO/IEC 15417-2013) «Автоматическая идентификация.
// Кодирование штриховое. Спецификация символики Code 128 (Код 128)».
//
// Структура штрихкода Code 128 достаточно проста. Штрихкод состоит из шести зон:
//
// * Белое поле;
// * Стартовый символ (Start);
// * Кодированная информация;
// * Проверочный символ (контрольный знак);
// * Остановочный (Stop) символ;
// * Белое поле.
//
// Символы штрихового кода Code 128 состоят из трёх штрихов и трёх
// промежутков. Штрихи и промежутки имеют модульное построение.
// Ширина каждого штриха и промежутка составляет от 1 до 4 модулей
// (1 модуль = 0,33 мм). Ширина знака равна 11 модулям. Остановочный
// (стоп) знак состоит из тринадцати модулей и имеет четыре штриха
// и три промежутка.
//
// Существует 107 шаблонов (patterns), каждый из которых кодируется
// 11 модулями, включающими 3 полоски (slim bars) и 3 промежутка (spaces).
// Исключение — стоп-шаблон [STOP] имеет 4 полоски и кодируется 13 модулями.
//
// Каждый шаблон декодируется по таблице, для некоторых шаблонов определены
// управляющие воздействия, определяющие режим декодирования.
//
// Один из трех стартовых шаблонов [START-A] (#103), [START-B] (#104)
// или [START-C] (#105) ограничивает код слева и определяет таблицу
// декодирования (A, B или C). Шаблон [STOP] (#106) ограничивает
// код справа. Перед шаблоном [STOP] идет шаблон контрольной суммы.
// Слева и справа от штрихкода должно быть свободное пространство
// (Quiet zone) шириной по крайней мере в 10 модулей.
//
// Шаблон [SHIFT] (#98 в таблицах A и B) позволяет переключаться между
// таблицами A и B. Шаблоны [Code C] (#99 в таблицах A и B), [Code B]
// (#100 в таблицах A и C) и [Code A] (#101 в таблицах B и C) управляют
// непосредственным включением соответствующей таблицы декодирования.
//
// Функциональные шаблоны [FNC1]-[FNC4] не определены.
//
// Контрольная сумма занимает один шаблон и должна быть добавлена перед
// шаблоном [STOP]. Шаги для расчета контрольной цифры таковы:
//
// 1. Начальное значение = значению шаблона [START] (103, 104 или 105).
//
// 2. Для каждого следующего шаблона в сообщении (исключая [STOP]):
// взять его значение, умножить его на свою позицию (позиция первого
// шаблона после [START] равна 1). Результат добавить к контрольной сумме.
//
// 3. Контрольной суммой будет считаться остаток от деления результата на 103.
//
// Значением (value) шаблона считается его порядковый номер в таблице (считаем с нуля).
//

/// <summary>
/// Штрих-код, поддерживающий не только цифры,
/// но и буквы латинского алфавита, а также специальные символы.
/// </summary>
public sealed class Code128
    : IBarcode
{
    #region Constants

    /// <summary>
    ///
    /// </summary>
    public const char FNC1 = (char)200;

    /// <summary>
    ///
    /// </summary>
    public const char FNC2 = (char)200;

    /// <summary>
    ///
    /// </summary>
    public const char FNC3 = (char)200;

    /// <summary>
    ///
    /// </summary>
    public const char FNC4 = (char)200;

    #endregion

    #region Properties

    /// <summary>
    /// Множитель для ширины полос.
    /// </summary>
    public float Weight { get; set; } = 3.0f;

    #endregion

    #region Private members

    // паттерны для отображения символов: A
    private static readonly Dictionary<char, string> _patternsA = new ()
    {
        [' '] = "11011001100",
        ['!'] = "11001101100",
        ['"'] = "11001100110",
        ['#'] = "10010011000",
        ['$'] = "10010001100",
        ['%'] = "10001001100",
        ['&'] = "10011001000",
        ['\''] = "10011000100",
        ['('] = "10001100100",
        [')'] = "11001001000",
        ['*'] = "11001000100",
        ['+'] = "11000100100",
        [','] = "10110011100",
        ['-'] = "10011011100",
        ['.'] = "10011001110",
        ['/'] = "10111001100",
        ['0'] = "10011101100",
        ['1'] = "10011100110",
        ['2'] = "11001110010",
        ['3'] = "11001011100",
        ['4'] = "11001001110",
        ['5'] = "11011100100",
        ['6'] = "11001110100",
        ['7'] = "11101101110",
        ['8'] = "11101001100",
        ['9'] = "11100101100",
        [':'] = "11100100110",
        [';'] = "11101100100",
        ['<'] = "11100110100",
        ['='] = "11100110010",
        ['>'] = "11011011000",
        ['?'] = "11011000110",
        ['@'] = "11000110110",
        ['A'] = "10100011000",
        ['B'] = "10001011000",
        ['C'] = "10001000110",
        ['D'] = "10110001000",
        ['E'] = "10001101000",
        ['F'] = "10001100010",
        ['G'] = "11010001000",
        ['H'] = "11000101000",
        ['I'] = "11000100010",
        ['J'] = "10110111000",
        ['K'] = "10110001110",
        ['L'] = "10001101110",
        ['M'] = "10111011000",
        ['N'] = "10111000110",
        ['O'] = "10001110110",
        ['P'] = "11101110110",
        ['Q'] = "11010001110",
        ['R'] = "11000101110",
        ['S'] = "11011101000",
        ['T'] = "11011100010",
        ['U'] = "11011101110",
        ['V'] = "11101011000",
        ['W'] = "11101000110",
        ['X'] = "11100010110",
        ['Y'] = "11101101000",
        ['Z'] = "11101100010",
        ['['] = "11100011010",
        ['\\'] = "11101111010",
        [']'] = "11001000010",
        ['^'] = "11110001010",
        ['_'] = "10100110000",
        [(char) 0] = "10100001100",
        [(char) 1] = "10010110000",
        [(char) 2] = "10010000110",
        [(char) 3] = "10000101100",
        [(char) 4] = "10000100110",
        [(char) 5] = "10110010000",
        [(char) 6] = "10110000100",
        [(char) 7] = "10011010000",
        [(char) 8] = "10011000010",
        [(char) 9] = "10000110100",
        [(char) 10] = "10000110010",
        [(char) 11] = "11000010010",
        [(char) 12] = "11001010000",
        [(char) 13] = "11110111010",
        [(char) 14] = "11000010100",
        [(char) 15] = "10001111010",
        [(char) 16] = "10100111100",
        [(char) 17] = "10010111100",
        [(char) 18] = "10010011110",
        [(char) 19] = "10111100100",
        [(char) 20] = "10011110100",
        [(char) 21] = "10011110010",
        [(char) 22] = "11110100100",
        [(char) 23] = "11110010100",
        [(char) 24] = "11110010010",
        [(char) 25] = "11011011110",
        [(char) 26] = "11011110110",
        [(char) 27] = "11110110110",
        [(char) 28] = "10101111000",
        [(char) 29] = "10100011110",
        [(char) 30] = "10001011110",
        [(char) 31] = "10111101000",
    };

    // паттерны для отображения символов: B
    private static readonly Dictionary<char, string> _patternsB = new ()
    {
        [' '] = "11011001100",
        ['!'] = "11001101100",
        ['"'] = "11001100110",
        ['#'] = "10010011000",
        ['$'] = "10010001100",
        ['%'] = "10001001100",
        ['&'] = "10011001000",
        ['\''] = "10011000100",
        ['('] = "10001100100",
        [')'] = "11001001000",
        ['*'] = "11001000100",
        ['+'] = "11000100100",
        [','] = "10110011100",
        ['-'] = "10011011100",
        ['.'] = "10011001110",
        ['/'] = "10111001100",
        ['0'] = "10011101100",
        ['1'] = "10011100110",
        ['2'] = "11001110010",
        ['3'] = "11001011100",
        ['4'] = "11001001110",
        ['5'] = "11011100100",
        ['6'] = "11001110100",
        ['7'] = "11101101110",
        ['8'] = "11101001100",
        ['9'] = "11100101100",
        [':'] = "11100100110",
        [';'] = "11101100100",
        ['<'] = "11100110100",
        ['='] = "11100110010",
        ['>'] = "11011011000",
        ['?'] = "11011000110",
        ['@'] = "11000110110",
        ['A'] = "10100011000",
        ['B'] = "10001011000",
        ['C'] = "10001000110",
        ['D'] = "10110001000",
        ['E'] = "10001101000",
        ['F'] = "10001100010",
        ['G'] = "11010001000",
        ['H'] = "11000101000",
        ['I'] = "11000100010",
        ['J'] = "10110111000",
        ['K'] = "10110001110",
        ['L'] = "10001101110",
        ['M'] = "10111011000",
        ['N'] = "10111000110",
        ['O'] = "10001110110",
        ['P'] = "11101110110",
        ['Q'] = "11010001110",
        ['R'] = "11000101110",
        ['S'] = "11011101000",
        ['T'] = "11011100010",
        ['U'] = "11011101110",
        ['V'] = "11101011000",
        ['W'] = "11101000110",
        ['X'] = "11100010110",
        ['Y'] = "11101101000",
        ['Z'] = "11101100010",
        ['['] = "11100011010",
        ['\\'] = "11101111010",
        [']'] = "11001000010",
        ['^'] = "11110001010",
        ['_'] = "10100110000",
        ['`'] = "10100001100",
        ['a'] = "10010110000",
        ['b'] = "10010000110",
        ['c'] = "10000101100",
        ['d'] = "10000100110",
        ['e'] = "10110010000",
        ['f'] = "10110000100",
        ['g'] = "10011010000",
        ['h'] = "10011000010",
        ['i'] = "10000110100",
        ['j'] = "10000110010",
        ['k'] = "11000010010",
        ['l'] = "11001010000",
        ['m'] = "11110111010",
        ['n'] = "11000010100",
        ['o'] = "10001111010",
        ['p'] = "10100111100",
        ['q'] = "10010111100",
        ['r'] = "10010011110",
        ['s'] = "10111100100",
        ['t'] = "10011110100",
        ['u'] = "10011110010",
        ['v'] = "11110100100",
        ['w'] = "11110010100",
        ['x'] = "11110010010",
        ['y'] = "11011011110",
        ['z'] = "11011110110",
        ['{'] = "11110110110",
        ['|'] = "10101111000",
        ['}'] = "10100011110",
        ['~'] = "10001011110",
    };

    // паттерны для отображения символов: C
    private static readonly Dictionary<string, string> _patternsC = new ()
    {
        ["00"] = "11011001100",
        ["01"] = "11001101100",
        ["02"] = "11001100110",
        ["03"] = "10010011000",
        ["04"] = "10010001100",
        ["05"] = "10001001100",
        ["06"] = "10011001000",
        ["07"] = "10011000100",
        ["08"] = "10001100100",
        ["09"] = "11001001000",
        ["10"] = "11001000100",
        ["11"] = "11000100100",
        ["12"] = "10110011100",
        ["13"] = "10011011100",
        ["14"] = "10011001110",
        ["15"] = "10111001100",
        ["16"] = "10011101100",
        ["17"] = "10011100110",
        ["18"] = "11001110010",
        ["19"] = "11001011100",
        ["20"] = "11001001110",
        ["21"] = "11011100100",
        ["22"] = "11001110100",
        ["23"] = "11101101110",
        ["24"] = "11101001100",
        ["25"] = "11100101100",
        ["26"] = "11100100110",
        ["27"] = "11101100100",
        ["28"] = "11100110100",
        ["29"] = "11100110010",
        ["30"] = "11011011000",
        ["31"] = "11011000110",
        ["32"] = "11000110110",
        ["33"] = "10100011000",
        ["34"] = "10001011000",
        ["35"] = "10001000110",
        ["36"] = "10110001000",
        ["37"] = "10001101000",
        ["38"] = "10001100010",
        ["39"] = "11010001000",
        ["40"] = "11000101000",
        ["41"] = "11000100010",
        ["42"] = "10110111000",
        ["43"] = "10110001110",
        ["44"] = "10001101110",
        ["45"] = "10111011000",
        ["46"] = "10111000110",
        ["47"] = "10001110110",
        ["48"] = "11101110110",
        ["49"] = "11010001110",
        ["50"] = "11000101110",
        ["51"] = "11011101000",
        ["52"] = "11011100010",
        ["53"] = "11011101110",
        ["54"] = "11101011000",
        ["55"] = "11101000110",
        ["56"] = "11100010110",
        ["57"] = "11101101000",
        ["58"] = "11101100010",
        ["59"] = "11100011010",
        ["60"] = "11101111010",
        ["61"] = "11001000010",
        ["62"] = "11110001010",
        ["63"] = "10100110000",
        ["64"] = "10100001100",
        ["65"] = "10010110000",
        ["66"] = "10010000110",
        ["67"] = "10000101100",
        ["68"] = "10000100110",
        ["69"] = "10110010000",
        ["70"] = "10110000100",
        ["71"] = "10011010000",
        ["72"] = "10011000010",
        ["73"] = "10000110100",
        ["74"] = "10000110010",
        ["75"] = "11000010010",
        ["76"] = "11001010000",
        ["77"] = "11110111010",
        ["78"] = "11000010100",
        ["79"] = "10001111010",
        ["80"] = "10100111100",
        ["81"] = "10010111100",
        ["82"] = "10010011110",
        ["83"] = "10111100100",
        ["84"] = "10011110100",
        ["85"] = "10011110010",
        ["86"] = "11110100100",
        ["87"] = "11110010100",
        ["88"] = "11110010010",
        ["89"] = "11011011110",
        ["90"] = "11011110110",
        ["91"] = "11110110110",
        ["92"] = "10101111000",
        ["93"] = "10100011110",
        ["94"] = "10001011110",
        ["95"] = "10111101000",
        ["96"] = "10111100010",
        ["97"] = "11110101000",
        ["98"] = "11110100010",
        ["99"] = "10111011110",
    };

    /// <summary>
    /// Кодирование данных.
    /// </summary>
    public static string Encode
        (
            string text
        )
    {
        Sure.NotNull (text);

        var result = new List<char>();

        return new string (result.ToArray());
    }

    #endregion

    #region Public methods

    /// <summary>
    /// Проверка, пригодны ли данные для штрих-кода.
    /// </summary>
    public bool Verify
        (
            BarcodeData data
        )
    {
        var message = data.Message;

        if (string.IsNullOrWhiteSpace (message))
        {
            return false;
        }

        foreach (var c in message)
        {
            if (!_patternsA.ContainsKey (c)
                && !_patternsB.ContainsKey (c))
            {
                return false;
            }
        }

        return true;
    }

    #endregion

    #region IBarcode members

    /// <inheritdoc cref="IBarcode.Symbology"/>
    public string Symbology { get; } = "Code128";

    /// <inheritdoc cref="IBarcode.DrawBarcode"/>
    public void DrawBarcode
        (
            BarcodeContext context
        )
    {
        Sure.NotNull (context);

        var data = context.Data;
        if (data is null || !Verify (data))
        {
            return;
        }

        var encoded = Encode (data.Message.ThrowIfNull());
        encoded = "00" + encoded + "00";
        var graphics = context.Graphics.ThrowIfNull();
        var bounds = context.Bounds;
        using var fore = new SolidBrush (Color.Black);
        using var back = new SolidBrush (Color.White);
        var position = bounds.Left;

        foreach (var c in encoded)
        {
            var rect = new RectangleF (position, bounds.Top, Weight, bounds.Height);
            var brush = c == '0' ? back : fore;
            graphics.FillRectangle (brush, rect);
            position += Weight;
        }
    }

    #endregion
}
